package org.itsolutions.javaBackup.gui.web.common;

import static java.util.concurrent.TimeUnit.SECONDS;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicLong;

import org.itsolutions.javaBackup.gui.web.common.components.IView;
import org.itsolutions.javaBackup.gui.web.common.components.JBNotification;
import org.itsolutions.javaBackup.logger.ELogLevel;
import org.itsolutions.javaBackup.logger.ILogger;
import org.itsolutions.javaBackup.logger.LoggerHelper;

import com.google.inject.Inject;
import com.vaadin.Application;
import com.vaadin.service.ApplicationContext.TransactionListener;
import com.vaadin.ui.AbstractSelect;
import com.vaadin.ui.ComponentContainer;

/**
 * 
 * @param <T> type
 * @author Tomas Fecko
 */
public abstract class AStatefullView<T> implements IView<T> {
	
	private static final ILogger	LOGGER = LoggerHelper.getLogger(AStatefullView.class);
	private boolean 				mWasInitCalled = false;
	private T 						mChild;
	private ApplicationDataHolder	mAppDataHolder;
	private final String			mViewUID = UUID.randomUUID().toString();

	/**
	 * 
	 * @return view
	 */
	protected abstract T initOnce();

	@Override
	public final T init() {
		if (!mWasInitCalled) {
			mWasInitCalled = true;
			try {
				mChild = initOnce();
				mAppDataHolder.getApplication().getContext().addTransactionListener(new TXListener<T>(this));
			} catch (Exception e) {
				LOGGER.log(ELogLevel.ERROR, e);
			}
		}
		return mChild;
	}

	/**
	 * @param pAppDataHolder the appDataHolder to set
	 */
	@Inject final void setAppDataHolder(ApplicationDataHolder pAppDataHolder) {
		mAppDataHolder = pAppDataHolder;
	}

	/**
	 * 
	 * @param pNotification notification to show
	 */
	protected final void showNotification(JBNotification pNotification) {
		ComponentContainer mainContainer = getMainContainer();
		if (pNotification == null || mainContainer == null || mainContainer.getWindow() == null) {
			return;
		}
		mainContainer.getWindow().showNotification(pNotification);
	}
	
	/**
	 * 
	 * @param <E> type of return value
	 * @param pSelectable abstractSelect component to select from
	 * @param pReturnClass return class
	 * @return list of selected values
	 */
	@SuppressWarnings("unchecked")
	protected final <E> List<E> getSelectedValues(AbstractSelect pSelectable, Class<E> pReturnClass) {
		Object selectedValue = pSelectable.getValue();
		List<E> returnList = new ArrayList<E>();
		if (selectedValue instanceof Collection<?> && !((Collection<?>) selectedValue).isEmpty()) {
			Collection<E> values = (Collection<E>) selectedValue;
			for (E value : values) {
				returnList.add(value);
			}
		} else if (selectedValue != null && selectedValue.getClass().equals(pReturnClass)) {
			returnList.add((E) selectedValue);
		}
		return returnList;
	}

	/**
	 * 
	 * @param <E> type of return value
	 * @param pSelectable abstractSelect component to select from
	 * @param pReturnClass return class
	 * @return list of selected values
	 */
	@SuppressWarnings("unchecked")
	protected final <E> E getSelectedValue(AbstractSelect pSelectable, Class<E> pReturnClass) {
		Object selectedValue = pSelectable.getValue();
		if (selectedValue != null && selectedValue.getClass().equals(pReturnClass)) {
			return (E) selectedValue;
		} else if (selectedValue instanceof Collection<?> && !((Collection<?>) selectedValue).isEmpty()) {
			Collection<E> values = (Collection<E>) selectedValue;
			for (E value : values) {
				return value;
			}
		}
		return null;
	}

	/**
	 * 
	 * @param pTransactionData transaction data
	 */
	protected void onTransactionStart(Object pTransactionData) {
	}

	/**
	 * 
	 * @param pTransactionData transaction data
	 */
	protected void onTransactionEnd(Object pTransactionData) {
	}

	/**
	 * Returns this view unique ID.
	 * @return the viewUID
	 */
	public final String getViewUID() {
		return mViewUID;
	}

	//CHECKSTYLE:OFF
	@Override
	public int hashCode() {
		//CHECKSTYLE:ON
		final int prime = 31;
		int result = 1;
		result = prime * result + ((mViewUID == null) ? 0 : mViewUID.hashCode());
		return result;
	}

	//CHECKSTYLE:OFF
	@Override
	@SuppressWarnings("rawtypes")
	public boolean equals(Object pObj) {
		//CHECKSTYLE:ON
		if (this == pObj) {
			return true;
		}
		if (pObj == null) {
			return false;
		}
		if (!(pObj instanceof AStatefullView)) {
			return false;
		}
		AStatefullView other = (AStatefullView) pObj;
		if (mViewUID == null) {
			if (other.mViewUID != null) {
				return false;
			}
		} else if (!mViewUID.equals(other.mViewUID)) {
			return false;
		}
		return true;
	}

	/**
	 * Vaadin Transactional listener.
	 * @param <T> type of view
	 */
	private static final class TXListener<T> implements TransactionListener {

		private static final long 		serialVersionUID = 7857782526720407062L;
		private static final ILogger	LOGGER = LoggerHelper.getLogger(TXListener.class);
		private static final long		TIMEOUT = SECONDS.toMillis(1);
		private final AStatefullView<T> mView;
		private final AtomicLong		mLastTimeTxWasCalled = new AtomicLong(0);

		/**
		 * @param pView
		 */
		private TXListener(AStatefullView<T> pView) {
			mView = pView;
		}

		@Override
		public void transactionStart(Application pApplication, Object pTransactionData) {
			if (checkIfReturn(pApplication) || System.currentTimeMillis() < mLastTimeTxWasCalled.get() + TIMEOUT) {
				return;
			}
			try {
				mLastTimeTxWasCalled.set(System.currentTimeMillis());
				mView.onTransactionStart(pTransactionData);
			} catch (Exception e) {
				LOGGER.log(ELogLevel.ERROR, e);
			}
		}

		@Override
		public void transactionEnd(Application pApplication, Object pTransactionData) {
			if (checkIfReturn(pApplication) || System.currentTimeMillis() < mLastTimeTxWasCalled.get() + TIMEOUT) {
				return;
			}
			try {
				mLastTimeTxWasCalled.set(System.currentTimeMillis());				
				mView.onTransactionEnd(pTransactionData);
			} catch (Exception e) {
				LOGGER.log(ELogLevel.ERROR, e);
			}
		}

		/**
		 * 
		 * @param pApplication vaadin application
		 * @return if true, onTransaction method shouldn'b be called
		 */
		private boolean checkIfReturn(Application pApplication) {
			return pApplication == null 
					|| mView.getMainContainer() == null 
					|| mView.getMainContainer().getApplication() == null
					|| !mView.getMainContainer().isVisible()
					|| mView.mAppDataHolder == null
					|| mView.mAppDataHolder.getApplicationEnum() == null
					|| !mView.mAppDataHolder.getApplicationEnum().name().equals(pApplication.getProperty(EGUIApplication.class.getName()));
		}
	}
}






